package com.boot.adminservice.map.service.strategy;

import com.boot.adminapi.map.constants.MapConstants;
import com.boot.adminservice.map.domain.dto.SysRegionDTO;
import com.boot.commoncache.utils.CacheUtil;
import com.boot.commonredis.service.RedisService;
import com.boot.commonredis.service.RedissonLockService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.github.benmanes.caffeine.cache.Cache;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 获取城市列表具体策略:互斥锁双重if保证第一个请求会查数据库并载入两级缓存，其它请求直接查缓存
 */
@Component("fromL2CacheFirstRequestLoadStrategy")
public class FromL2CacheFirstRequestLoadStrategy implements GetCityListStrategy {

    /**
     * Redis缓存服务对象
     */
    @Autowired
    private RedisService redisService;

    /**
     * 本地缓存服务对象
     */
    @Autowired
    private Cache<String, Object> caffeineCache;

    /**
     * 数据库查询策略对象
     */
    @Autowired
    private FromDBStrategy fromDBStrategy;

    /**
     * 分布式锁
     */
    @Autowired
    private RedissonLockService redissonLockService;


    /**
     * 城市列表查询 V5 分布式锁方案
     * @return 城市列表信息
     */
    @Override
    public List<SysRegionDTO> getCityList() {
        // 1 先查询两级缓存
        List<SysRegionDTO> cache = CacheUtil.getL2Cache(redisService, MapConstants.CACHE_MAP_CITY_KEY, new TypeReference<List<SysRegionDTO>>() {
        }, caffeineCache);
        // 2 两级缓存未命中，双重if加锁方案(类似单例模式):只让一个请求去加锁并构建两级缓存
        if (cache == null) {
            // 3 获取分布式锁(过期时间-1表示自动续期)
            RLock lockInstance = redissonLockService.acquire(MapConstants.LOCKER_MAP_CITY, -1);
            // 4 进入第一层if但未拿到锁的请求需要再查缓存更新cache状态(其实缓存中已经有第一个请求载入的数据，如果再不查询缓存，cache就为null，数据不一致)
            cache = CacheUtil.getL2Cache(redisService, MapConstants.CACHE_MAP_CITY_KEY, new TypeReference<List<SysRegionDTO>>() {
            }, caffeineCache);
            if (cache == null) {
                // 5 查数据库，把城市摘出来
                List<SysRegionDTO> result = fromDBStrategy.getCityList();
                // 6 缓存空结果也存储，防止缓存穿透
                if (result == null) {
                    result = Collections.emptyList();
                }
                // 7 载入两级缓存
                CacheUtil.setL2Cache(redisService, MapConstants.CACHE_MAP_CITY_KEY, result, caffeineCache, 120L, TimeUnit.MINUTES);
                // 8 释放分布式锁
                redissonLockService.releaseLock(lockInstance);
                return result;
            }
            // 9 释放分布式锁
            // 此处释放的是进入外层if但未先拿到分布式锁的请求，待第一个请求载入缓存后，其它请求缓存命中，不用进入第二个if，拿到分布式锁后及时释放
            redissonLockService.releaseLock(lockInstance);
        }
        // 10 在第一个请求载入数据到缓存中，其它请求就会缓存命中从而直接返回
        return cache;
    }

    /**
     * 按首字母A-Z分类的城市列表查询 V5 分布式锁方案
     * @return 城市列表信息
     */
    @Override
    public Map<String, List<SysRegionDTO>> getCityPyList() {
        // 1 先查询两级缓存
        Map<String, List<SysRegionDTO>>  cache = CacheUtil.getL2Cache(redisService, MapConstants.CACHE_MAP_CITY_PINYIN_KEY, new TypeReference<Map<String, List<SysRegionDTO>> >() {
        }, caffeineCache);
        // 2 两级缓存未命中，双重if加锁方案(类似单例模式):只让一个请求去加锁并构建两级缓存
        if (cache == null) {
            // 3 获取分布式锁(过期时间-1表示自动续期)
            RLock lockInstance = redissonLockService.acquire(MapConstants.LOCKER_MAP_CITY_PINYIN, -1);
            // 4 进入第一层if但未拿到锁的请求需要再查缓存更新cache状态(其实缓存中已经有第一个请求载入的数据，如果再不查询缓存，cache就为null，数据不一致)
            cache = CacheUtil.getL2Cache(redisService, MapConstants.CACHE_MAP_CITY_PINYIN_KEY, new TypeReference<Map<String, List<SysRegionDTO>>>() {
            }, caffeineCache);
            if (cache == null) {
                // 5 查数据库，把城市摘出来
                Map<String, List<SysRegionDTO>> result = fromDBStrategy.getCityPyList();
                // 6 缓存空结果也存储，防止缓存穿透
                if (result == null) {
                    result = Collections.emptyMap();
                }
                // 7 载入两级缓存
                CacheUtil.setL2Cache(redisService, MapConstants.CACHE_MAP_CITY_PINYIN_KEY, result, caffeineCache, 120L, TimeUnit.MINUTES);
                // 8 释放分布式锁
                redissonLockService.releaseLock(lockInstance);
                return result;
            }
            // 9 释放分布式锁
            // 此处释放的是进入外层if但未先拿到分布式锁的请求，待第一个请求载入缓存后，其它请求缓存命中，不用进入第二个if，拿到分布式锁后及时释放
            redissonLockService.releaseLock(lockInstance);
        }
        // 9 在第一个请求载入数据到缓存中，其它请求就会缓存命中从而直接返回
        return cache;
    }
}
